package com.flyqiu.flow.core.impl;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.flyqiu.common.bean.FlyQiuBeanManage;
import com.flyqiu.common.tools.StringTools;
import com.flyqiu.common.transaction.FlyQiuTransactionManage;
import com.flyqiu.common.user.FlyQiuUserTools;
import com.flyqiu.flow.api.format.FlyQiuFlowBpmnFormatManage;
import com.flyqiu.flow.api.cache.BpmnModelCache;
import com.flyqiu.flow.api.context.FlyQiuFlowGlobalContext;
import com.flyqiu.flow.api.deploy.IFlowDeploy;
import com.flyqiu.flow.api.modelSource.FlowModelSourceManage;
import com.flyqiu.flow.api.service.FlowHistoryService;
import com.flyqiu.flow.api.service.FlowModelService;
import com.flyqiu.flow.api.service.FlowRunningService;
import com.flyqiu.flow.bpmn.FlyQiuBpmModeler;
import com.flyqiu.flow.common.enums.ProcDefStatus;
import com.flyqiu.flow.mapper.ProcDeployDefMapper;
import com.flyqiu.flow.mapper.ProcModelMapper;
import com.flyqiu.flow.mapper.ProcModelSourceMapper;

import com.flyqiu.flow.model.entity.ProcDeployDef;
import com.flyqiu.flow.model.entity.ProcDeploySource;
import com.flyqiu.flow.model.entity.ProcModel;
import com.flyqiu.flow.model.entity.ProcModelSource;
import com.flyqiu.flow.model.entity.hi.ProcHiInst;
import com.flyqiu.flow.model.entity.ru.ProcRuInst;

import java.time.LocalDateTime;
import java.util.List;

public class FlowModelServiceImpl implements FlowModelService {

    private final ProcModelMapper procModelMapper;
    private final ProcDeployDefMapper procDeployDefMapper;

    private final ProcModelSourceMapper procModelSourceMapper;
    private final FlowModelSourceManage procModelSourceManage;
    private final FlyQiuFlowBpmnFormatManage flowBpmnFormatManage;
    private final FlyQiuFlowGlobalContext procGlobalContext;
    private final FlyQiuTransactionManage transactionManage;
    private final FlyQiuBeanManage procBeanManage;

    public FlowModelServiceImpl(FlyQiuFlowGlobalContext procGlobalContext) {
        this.procGlobalContext = procGlobalContext;
        transactionManage = procGlobalContext.getTransactionManage();
        procBeanManage = procGlobalContext.getBeanManage();
        this.procModelSourceManage = procGlobalContext.getFlowModelSourceManage();
        this.flowBpmnFormatManage = procGlobalContext.getFlowBpmnFormatManage();
        this.procModelMapper = procBeanManage.getMapper(ProcModelMapper.class);
        this.procDeployDefMapper = procBeanManage.getMapper(ProcDeployDefMapper.class);
        this.procModelSourceMapper = procBeanManage.getMapper(ProcModelSourceMapper.class);

    }


    @Override
    public void saveModel(ProcModel model) {
        if (StringUtils.checkValNull(model.getModelKey())) {
            throw new RuntimeException("modelKey Cannot be null or empty");
        }
        if (StringUtils.checkValNull(model.getModelName())) {
            throw new RuntimeException("modelName be null or empty");
        }
        LambdaQueryWrapper<ProcModel> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ProcModel::getModelKey, model.getModelKey());
        lambdaQueryWrapper.eq(StringTools.isNotBlank(FlyQiuUserTools.getTenantId()), ProcModel::getTenantId, FlyQiuUserTools.getTenantId());
        ProcModel procModel = procModelMapper.selectOne(lambdaQueryWrapper);
        if (procModel != null) {
            model.setId(procModel.getId());
        }
        model.setTenantId(FlyQiuUserTools.getTenantId());
        if (StringTools.isEmpty(model.getId())) {
            model.setCreateTime(LocalDateTime.now());
            procModelMapper.insert(model);
            return;
        }
        model.setUpdateTime(LocalDateTime.now());
        procModelMapper.updateById(model);
    }

    @Override
    public void deleteModel(String modelId) {
        ProcModel procModel = procModelMapper.selectById(modelId);
        if (procModel == null) return;
        LambdaQueryWrapper<ProcModelSource> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ProcModelSource::getModelKey, procModel.getModelKey());
        lambdaQueryWrapper.eq(StringTools.isNotBlank(FlyQiuUserTools.getTenantId()), ProcModelSource::getTenantId, FlyQiuUserTools.getTenantId());
        procModelSourceMapper.delete(lambdaQueryWrapper);
        procModelMapper.deleteById(procModel.getId());
    }

    @Override
    public List<ProcModel> listProcModel(Wrapper<ProcModel> wrapper) {
        return procModelMapper.selectList(wrapper);
    }

    @Override
    public Page<ProcModel> pageProcModel(Page<ProcModel> page, Wrapper<ProcModel> wrapper) {
        return procModelMapper.selectPage(page, wrapper);
    }

    @Override
    public long countProcModel(Wrapper<ProcModel> wrapper) {
        return procModelMapper.selectCount(wrapper);
    }

    @Override
    public ProcModel getProcModel(Wrapper<ProcModel> wrapper) {
        List<ProcModel> procModels = listProcModel(wrapper);
        if (procModels.isEmpty()) return null;
        return procModels.get(0);
    }

    @Override
    public void saveModelEditorSource(String modelSource, String currentFormat) {
        if (StringTools.isAllBlank(modelSource)) {
            return;
        }
        if (StringTools.isAllBlank(currentFormat)) {
            throw new RuntimeException("currentFormat is null");
        }
        JSONObject startBpmn = flowBpmnFormatManage.convertStartBpmn(modelSource, currentFormat);
        saveModelEditorSource(startBpmn);
    }

    @Override
    public void saveModelEditorSource(String modeKey, JSONObject source) {
        LambdaQueryWrapper<ProcModelSource> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProcModelSource::getModelKey, modeKey);
        wrapper.eq(StringTools.isNotBlank(FlyQiuUserTools.getTenantId()), ProcModelSource::getTenantId, FlyQiuUserTools.getTenantId());
        ProcModelSource modelSource = procModelSourceMapper.selectOne(wrapper);
        if (modelSource == null) {
            modelSource = new ProcModelSource();
        }
        source.put(FlyQiuBpmModeler.PROC_MAIN_DEF_KEY, modeKey);
        modelSource.setModelSource(source);
        modelSource.setModelKey(modeKey);
        modelSource.setTenantId(FlyQiuUserTools.getTenantId());
        if (StringUtils.checkValNull(modelSource.getId())) {
            procModelSourceMapper.insert(modelSource);
            return;
        }
        procModelSourceMapper.updateById(modelSource);
    }


    @Override
    public JSONObject getModelEditorSource(String modeKey) {
        LambdaQueryWrapper<ProcModelSource> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProcModelSource::getModelKey, modeKey);
        wrapper.eq(StringTools.isNotBlank(FlyQiuUserTools.getTenantId()), ProcModelSource::getTenantId, FlyQiuUserTools.getTenantId());
        ProcModelSource modelSource = procModelSourceMapper.selectOne(wrapper);
        if (modelSource == null) return null;
        return modelSource.getModelSource();
    }

    @Override
    public String getModelEditorSource(String modeKey, String targetFormat) {
        JSONObject editorSource = getModelEditorSource(modeKey);
        if (editorSource == null) {
            return null;
        }
        return flowBpmnFormatManage.convertTargetFormat(editorSource, targetFormat);
    }

    /**
     * 发布流程模型
     *
     * @param modelId 模型id
     * @return 模型发布定义
     */
    @Override
    public ProcDeployDef deployModel(String modelId) {
        ProcModel procModel = getProcModel(modelId);
        if (procModel == null) {
            throw new RuntimeException("Could not find ProcModel with modeId: " + modelId);
        }
        return deployModel(procModel);
    }

    @Override
    public void deleteDeploy(String deployId) {
        BpmnModelCache bpmnModelCache = procModelSourceManage.getBpmModeler(deployId, false);
        if (bpmnModelCache == null) {
            return;
        }
        ProcDeployDef procDeployDef = bpmnModelCache.deployDef();
        transactionManage.apply(() -> {
            LambdaQueryWrapper<ProcRuInst> ruInstQueryWrapper = new LambdaQueryWrapper<>();
            ruInstQueryWrapper.eq(ProcRuInst::getProcDeployDefId, deployId);
            FlowRunningService flowRunningService = procBeanManage.getBean(FlowRunningService.class);
            long ruCount = flowRunningService.countProcInst(ruInstQueryWrapper);
            if (ruCount > 0) {
                throw new RuntimeException("Cannot delete a process definition that contains a running process");
            }
            LambdaQueryWrapper<ProcHiInst> hiInstQueryWrapper = new LambdaQueryWrapper<>();
            hiInstQueryWrapper.eq(ProcRuInst::getProcDeployDefId, deployId);
            FlowHistoryService flowHistoryService = procBeanManage.getBean(FlowHistoryService.class);
            long hiCount = flowHistoryService.countProcInst(hiInstQueryWrapper);
            if (hiCount > 0) {
                throw new RuntimeException("Cannot delete process definitions that contain historical process instances");
            }
            IFlowDeploy deploy = procGlobalContext.getFlowDeployManage().getDeploy(procDeployDef.getProcExecuteType());
            deploy.deleteDeploy(procDeployDef);
            procDeployDefMapper.deleteById(deployId);
        });

    }

    @Override
    public ProcDeployDef getProcDeployDef(Wrapper<ProcDeployDef> wrapper) {
        return procDeployDefMapper.selectOne(wrapper);
    }


    @Override
    public ProcDeployDef deployModel(String modelId, String procDefKey, String procName, String category) {
        ProcModel procModel = procModelMapper.selectById(modelId);
        if (procModel == null) {
            throw new RuntimeException("modelKey Cannot be null or empty");
        }
        return deployModel(procModel);
    }

    private ProcDeployDef deployModel(ProcModel procModel) {
        if (StringUtils.checkValNull(procModel.getModelKey())) {
            throw new RuntimeException("procModel.getModelKey() cannot be empty or null");
        }
        if (StringUtils.checkValNull(procModel.getModelName())) {
            throw new RuntimeException("procModel.getModelName() cannot be empty or null");
        }
        if (StringUtils.checkValNull(procModel.getModelCategory())) {
            throw new RuntimeException("procModel.getModelCategory() cannot be empty or null");
        }
        LambdaQueryWrapper<ProcModelSource> procSourceWrapper = new LambdaQueryWrapper<>();
        procSourceWrapper.eq(ProcModelSource::getModelKey, procModel.getModelKey());
        procSourceWrapper.eq(StringTools.isNotBlank(FlyQiuUserTools.getTenantId()), ProcModelSource::getTenantId, FlyQiuUserTools.getTenantId());

        ProcModelSource procModelSource = procModelSourceMapper.selectOne(procSourceWrapper);
        if (procModelSource == null) {
            throw new RuntimeException("No model source with modelKey:" + procModel.getModelKey() + " found");
        }
        if (procModelSource.getModelSource() == null || procModelSource.getModelSource().isEmpty()) {
            throw new RuntimeException("No model source with modelKey:" + procModel.getModelKey() + " found");
        }
        JSONObject procSource = procModelSource.getModelSource();
        return deployModel(procSource, procModel.getModelKey(), procModel.getModelName(), procModel.getModelCategory());
    }

    @Override
    public ProcDeployDef deployModel(JSONObject modelSource, String procDefKey, String procName, String category) {
        String procExecuteType = procGlobalContext.getFlowDeployManage().getDefProcExecuteTypeKey();
        return deployModel(modelSource, procExecuteType, procDefKey, procName, category);
    }

    @Override
    public ProcDeployDef deployModel(JSONObject modelSource, String procExecuteType, String procDefKey, String procName, String category) {
        IFlowDeploy deploy = procGlobalContext.getFlowDeployManage().getDeploy(procExecuteType);
        return procGlobalContext.getTransactionManage().apply(() -> {
            ProcDeploySource deploySource = deploy.deploy(modelSource, procDefKey, procName, category);
            if (deploySource == null) {
                throw new RuntimeException("deploySource cannot be  null");
            }
            if (StringTools.isAllBlank(deploySource.getId())) {
                throw new RuntimeException("deploySource.getId() cannot be null or Empty ");
            }
            ProcDeployDef deployDef = new ProcDeployDef();
            deployDef.setProcDeployVersion(deploySource.getDeploySourceVersion());
            deployDef.setProcDeployDefKey(procDefKey);
            deployDef.setProcDeployName(procName);
            deployDef.setProcDeployCategory(category);
            deployDef.setProcExecuteType(procExecuteType);
            deployDef.setProcDefStatus(ProcDefStatus.SUSPEND.getValue());
            deployDef.setTenantId(FlyQiuUserTools.getTenantId());
            deployDef.setDeploySourceId(deploySource.getId());
            deployDef.setCreateTime(LocalDateTime.now());
            procDeployDefMapper.insert(deployDef);
            return deployDef;
        });
    }

    @Override
    public void updateDeployDefStatus(String deployDefId, ProcDefStatus procDefStatus) {
        if (StringTools.isBlank(deployDefId)) {
            throw new RuntimeException("deployDefId cannot be null or Empty ");
        }
        if (procDefStatus == null) {
            throw new RuntimeException(" procDefStatus  cannot be null ");
        }
        ProcDeployDef procDeployDef = new ProcDeployDef();
        procDeployDef.setId(deployDefId);
        procDeployDef.setProcDefStatus(procDefStatus.getValue());
        procDeployDefMapper.updateById(procDeployDef);
    }

    @Override
    public ProcDeployDef lastVersionProcDeployDef(String procDeployDefKey, String tenantId) {
        LambdaQueryWrapper<ProcDeployDef> procDeployWrapper = new LambdaQueryWrapper<>();
        procDeployWrapper.eq(ProcDeployDef::getProcDeployDefKey, procDeployDefKey);
        procDeployWrapper.eq(ProcDeployDef::getProcDefStatus, ProcDefStatus.ACTIVATE.getValue());
        if (StringUtils.checkValNull(tenantId)) {
            tenantId = FlyQiuUserTools.getTenantId();
        }
        procDeployWrapper.eq(StringTools.isNotBlank(tenantId), ProcDeployDef::getTenantId, tenantId);
        procDeployWrapper.orderByDesc(ProcDeployDef::getProcDeployVersion);
        Page<ProcDeployDef> page = new Page<>();
        page.setCurrent(1);
        page.setSize(1);
        page = procDeployDefMapper.selectPage(page, procDeployWrapper);
        List<ProcDeployDef> records = page.getRecords();
        if (records.isEmpty()) {
            return null;
        }
        return records.get(0);
    }


    @Override
    public List<ProcDeployDef> listProcDeploy(Wrapper<ProcDeployDef> wrapper) {
        return procDeployDefMapper.selectList(wrapper);
    }

    @Override
    public Page<ProcDeployDef> pageProcDeploy(Page<ProcDeployDef> page, Wrapper<ProcDeployDef> wrapper) {
        return procDeployDefMapper.selectPage(page, wrapper);
    }

    @Override
    public long countProcDeploy(Wrapper<ProcDeployDef> wrapper) {
        return procDeployDefMapper.selectCount(wrapper);
    }

    /**
     * 获取流程模型资源信息
     *
     * @param procDeployDefId 流程发布定义id
     * @return StartBpmModeler
     */
    @Override
    public FlyQiuBpmModeler getBpmModeler(String procDeployDefId) {
        BpmnModelCache bpmModeler = procModelSourceManage.getBpmModeler(procDeployDefId, false);
        if (bpmModeler == null) {
            return null;
        }
        return bpmModeler.bpmModeler();
    }

    @Override
    public String getBpmModeler(String procDeployDefId, String targetFormat) {
        BpmnModelCache bpmModeler = procModelSourceManage.getBpmModeler(procDeployDefId, false);
        if (bpmModeler == null) {
            return null;
        }
        FlyQiuBpmModeler flyQiuBpmModeler = bpmModeler.bpmModeler();
        JSONObject bpmn = flyQiuBpmModeler.getBpmn();
        return flowBpmnFormatManage.convertTargetFormat(bpmn, targetFormat);
    }

}
